package GIT_Hooks_Common;

use strict;
use integer;	# Floating point not needed.  Speed things up a bit.
use Exporter;

use threads;
use threads::shared;
use Data::Dumper::Simple;
use XML::Simple;
use File::Spec::Functions qw(rel2abs);
use File::Basename qw(dirname basename);
use Cwd;
use Sys::CPU;
use IPC::Open2;

$Data::Dumper::Sortkeys = 1;

our @ISA				= qw( Exporter );
our @EXPORT				= qw(
	Debug
	parse_revisions
	grab_input
	@ERRORS
	$DEBUG
	$HANDLE
	$LOG_FILENAME
	$SCRIPT_PATH
	$SCRIPT_NAME
	$THIS_PATH
	$GIT_PATH
	$GIT_P4
	$GIT_VERSION
	$GIT_VERSION_STR
	$GIT_CONFIG
	$HOOK_TIMEOUT
	$MachineUbuntu
	$MachineRhel
	$MachineRhel5
);
our @EXPORT_OK			= qw();
our %EXPORT_TAGS		= ();

my $VERSION				= '1.3.20120228';
our $DEBUG				= 0;
our $SCRIPT_PATH		= dirname( rel2abs( $0 ) );
$SCRIPT_PATH			=~ s/\\/\//g;
our $SCRIPT_NAME		= basename($0);
our $LOG_FILENAME		= $SCRIPT_NAME;
$LOG_FILENAME			=~ s/\.pl/.log/;
our $THIS_PATH			= getcwd();
our $HOOK_TIMEOUT		= 300; # This is a keep-alive style timeout
our $HANDLE;
our $GIT_PATH			= '/tools/bin/git';
our $GIT_P4				= '/tools/bin/git-p4';

my $DBG_FILENAME		= "$SCRIPT_PATH/Logs/$LOG_FILENAME";
my $CACHE_FILENAME		= "$SCRIPT_PATH/Logs/manifests.cache";
my $CONFIG_XML			= "$SCRIPT_PATH/config.xml";
my $TEST_MODE			= 0;
my $REPOS_PATH			= '/projects/mobcom_andrgit/scm/git_repos/mpg_manifests/manifest.git';
my $MSP_GIT_REPO_PATH	= '';
my $FIRST				= 1;
my $GIT_URIS_SEARCH		= 'git://mobcom-git.sj.broadcom.com|ssh://mobcomgit@mobcom-git.sj.broadcom.com';
my $GIT_URIS_REPLACE	= '/projects/mobcom_andrgit/scm';
my $PROJECTS;

if ($SCRIPT_PATH =~ /rkelsch/) {
	$CONFIG_XML			= "$SCRIPT_PATH/config_tests.xml";
	$TEST_MODE			= 1;
	$GIT_URIS_REPLACE	= '/projects/mobcom_andrwks_ext10_scratch/users/rkelsch/testrepo2';
	$MSP_GIT_REPO_PATH	= '/projects/mobcom_andrwks/users/rkelsch/msp';
}

chomp(our $MachineUbuntu	= `uname -a | grep -i ubuntu | wc -l`);
chomp(our $MachineRhel		= `uname -a | grep ELsmp | wc -l`);
chomp(our $MachineRhel5		= `uname -a | grep el5 | wc -l`);
if ( $MachineUbuntu == 1 ) {
	Debug('Ubuntu Server GIT: /tools/oss/packages/x86_64-rhel5/git/1.7.3.2/bin/git');
	$GIT_PATH = '/tools/oss/packages/x86_64-rhel5/git/1.7.3.2/bin/git';
} elsif( $MachineRhel == 1 ) {
	Debug('Rhel Server GIT: /tools/oss/packages/x86_64-rhel4/git/1.7.3.2/bin/git');
	$GIT_PATH = '/tools/oss/packages/x86_64-rhel4/git/1.7.3.2/bin/git';
} elsif( $MachineRhel5 == 1 ) {
	Debug('Rhel5 Server GIT: /usr/bin/git');
	$GIT_PATH = '/usr/bin/git';
} elsif (! -e $GIT_PATH) {
	Debug('Unknown GIT Path:	git');
	$GIT_PATH = 'git';
}

#$SIG{'PIPE'} = 'IGNORE';
$SIG{'ALRM'} = \&timeout; # Place where timeout timer goes on timeout
alarm($HOOK_TIMEOUT); # This acts as a keep-alive timer.  If not reset, it kills the script.

our $GIT_VERSION_STR	= '';
our $GIT_VERSION		= '';
our $GIT_CONFIG			= {};
{ # no need to keep garbage variables
	my $cmd				= "$GIT_PATH --version";
	$GIT_VERSION_STR	= `$cmd`;
	chomp($GIT_VERSION_STR);
	$GIT_VERSION_STR	=~ s/git version //i;
	my @ver				= split(/\./,$GIT_VERSION_STR);
	$GIT_VERSION		= sprintf('%02d%02d%02d%02d',$GIT_VERSION_STR); # convert to a parsable number
	%{$GIT_CONFIG}		= map(
								{
									$_ =~ s/\./_/;
									split(/=/,$_);
								}
								split(/\n/,`$GIT_PATH config --list`)
							);
	}

our @ERRORS = ();


END {
	close($HANDLE) if (defined($HANDLE));
}

sub new {
	my $class	= shift;
	my $self	= shift;
	my $dirpath	= shift;
	
	# This overrides the hook completely and exits before ANY processing.  The override can be ANYWHERE in the commit message.
	# NOTE:  This will override ALL commits in the push and allow them through.  Use with caution!
	if ($self->{'message'} =~ /<<<<HOOK_OVERRIDE>>>>/s) {
		Debug('==== HOOK OVERRIDE DETECTED == END > 0 ' . '='x80);
		exit(0);
	}

	# Continue as normal
	Debug("USER:  $ENV{'USER'}");
	my $this_user_email = "$ENV{'USER'}\@broadcom.com";
	$self->{'CONFIG'}->{'stop_on_first_failure'}	= 1;
	$self->{'CONFIG'}->{'max_threads'}				= Sys::CPU::cpu_count();
	Debug("$self->{'CONFIG'}->{'max_threads'} CPU cores available for threading.");
	Debug("GIT info:\n" . Dumper($GIT_CONFIG));
	($self->{'userid'})	= $self->{'author'} =~ /<(\S+)\@/;
	$self->{'email'}	= $self->{'userid'} . '@broadcom.com';
	if ($self->{'email'} !~ /$GIT_CONFIG->{'user_email'}/i) {
		$self->{'email'} .= ',' . $GIT_CONFIG->{'user_email'};
	}
	if ($self->{'email'} !~ /$this_user_email/i) {
		$self->{'email'} .= ',' . $this_user_email;
	}
	bless $self, $class;
	if ($self->{'userid'} ne 'mcsi_user' && $GIT_CONFIG->{'user_name'} ne 'mcsi_user') {
		$self->{'wrong_user'} = 0;
		my $cmd = "$GIT_PATH branch --contains $self->{'commit_id'}";
		Debug("Running command '$cmd'");
		my $response = `$cmd`;
		$response =~ s/\n/, /sg;
		$response =~ s/^, //;
		Debug("'$cmd' response was:\n$response\n");
		$self->{'branches'}		= $self->trim($response);
		$self->{'description'}	= $self->{'message'};
		$self->{'description'}	=~ s/<Please remove this line and replace .*?>|There MUST be information in here|Description of commit|<Please substitute this line with .+?>|\r+//sg;
		my ($first,$second,$counter);
		($self->{'pre_description'}) = $self->{'description'} =~ /^(.*)/m;
		chomp($self->{'pre_description'});
		$self->{'pre_description'}	= $self->trim($self->{'pre_description'});
		$self->{'valid_template'}	= 1;
		$self->load_config($dirpath);
		if ($self->{'pre_description'} =~ /\[|\]/ || $self->{'pre_description'} eq '') {
			$self->{'pre_description'}	= '';
			$self->{'valid_template'}	= 0;
			Debug('Pre-description not found.  Invalid Template');
		}
		if ($self->{'description'} !~ /\[|\]/) {
			$self->{'valid_template'} = 0;
			Debug('Commit description does not contain a valid Broadcom template.  Invalid Template.');
		} else {
			my $size = scalar(@{$self->{'TEMPLATE_ORDER'}});
			$self->{'TEMPLATE_VALIDATE'}->{'testing'} = 0; # Testing is not a true field but a container
			# This validates the template.  I suggest you do not modify this code!
			for (my $count=0;$count<$size;$count++) {
				$first	= $self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]};
				$first	=~ s/^\-//;
				$first	=~ s#/#\\/#g;
				$first	=~ s/#/\\#/g;
				Debug("FIRST: $first");
				if ($count >= ($size - 1)) {
					if ($self->{'description'} =~ m#\[$first\]\n(.*)$#s) {
						my $change = $1 || '';
						chomp($change);
						Debug("$first = $change");
						my $temp = $change;
						$temp =~ s/\n+|\r+|\s+//sg;
						if ($temp eq '') {
							if ($self->{'TEMPLATE_VALIDATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]}) {
								$self->{'valid_template'} = 0;
								Debug("Template field $self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]} is blank.  Invalid template.");
							}
							$self->{$self->{'TEMPLATE_ORDER'}->[$count]} = '';
						} else {
							$self->{$self->{'TEMPLATE_ORDER'}->[$count]} = $change;
						}
					} else {
						Debug('Problem with search 0, messed up template?');
						if ($self->{'TEMPLATE_VALIDATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]}) {
							$self->{'valid_template'} = 0;
							Debug('Invalid template.');
						}
					}
				} else {
					$second = $self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count+1]};
					$second =~ s/^\-//;
					$second =~ s#/#\\/#g;
					$second =~ s/#/\\#/g;
					Debug("Searching for \[$first\] ... \[$second\]");
					if ($self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]} =~ /^\-/) {
						if ($self->{'description'} =~ m#\[$first\]\n(.+?)\s*\[$second\]#s) {
							my $change = $1 || '';
							chomp($change);
							Debug("$first = $change, $second");
							my $temp = $change;
							$temp =~ s/\n+|\r+|\s+//sg;
							if ($temp eq '') {
								$self->{$self->{'TEMPLATE_ORDER'}->[$count]} = '';
								if ($self->{'TEMPLATE_VALIDATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]}) {
									$self->{'valid_template'} = 0;
									Debug("Template field $self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]} is blank.  TEMPLATE_VALIDATE for '$self->{'TEMPLATE_ORDER'}->[$count]' = $self->{'TEMPLATE_VALIDATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]}, Invalid template. (S1)");
								} else {
									Debug("Template field $self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]} is blank.  However, TEMPLATE_VALIDATE for '$self->{'TEMPLATE_ORDER'}->[$count]' = $self->{'TEMPLATE_VALIDATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]}, not invalidating template. (S1)");
								}
							} else {
								$self->{$self->{'TEMPLATE_ORDER'}->[$count]} = $change;
								Debug("Template field $self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]} is not blank, not invalidating template. (S1)");
							}
						} else {
							Debug('Problem with search 1, messed up template?');
							if ($self->{'TEMPLATE_VALIDATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]}) {
								$self->{'valid_template'} = 0;
								Debug('Invalid template.');
							}
						}
					} else {
						my $change;
						$counter = $count + 1;
						if ($count == 0) {
							Debug('Looping through Defect label alternatives');
							my $counting = 0;
							foreach $first ($self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]},'Defect','Enhancement','New Feature') {
								$second = $self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$counter]};
								Debug("Searching for \[$first\] ... \[$second\]");
								if ($self->{'description'} =~ m#\[$first\]\n(.+?)\s*\[$second\]#s) {
									$change = $1 || '';
									chomp($change);
									Debug("$first = $change, $second");
									if ($change ne '') {
										Debug("Template field $self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]} is not blank, not invalidating template. (S1)");
										last;
									}
								} else { # 
									if ($self->{'TEMPLATE_VALIDATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]}) {
										$counting++;
									}
								}
							}
							Debug('Defect label alternatives checked');
							if ($counting >= 4) {
								$self->{'valid_template'} = 0;
								Debug('Invalid template. (S2)');
							}
						} else {
							if ($self->{'description'} =~ m#\[$first\]\n(.+?)\s*\[$second\]#s) {
								$change = $1 || '';
								chomp($change);
							} else {
								Debug('Problem with search 3, messed up template?');
								if ($self->{'TEMPLATE_VALIDATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]}) {
									$self->{'valid_template'} = 0;
									Debug('Invalid template.');
								}
							}
						}
						Debug("$first = $change, $second");
						my $temp = $change;
						$temp =~ s/\n+|\r+|\s+//sg;
						if ($temp eq '') {
							if ($self->{'TEMPLATE_VALIDATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]}) {
								$self->{'valid_template'} = 0;
								Debug("Template field $self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]} is blank.  TEMPLATE_VALIDATE for '$self->{'TEMPLATE_ORDER'}->[$count]' = $self->{'TEMPLATE_VALIDATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]}, Invalid template. (S3)");
							} else {
								Debug("Template field $self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]} is blank.  However, TEMPLATE_VALIDATE for '$self->{'TEMPLATE_ORDER'}->[$count]' = $self->{'TEMPLATE_VALIDATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]}, not invalidating template. (S3)");
							}
							$self->{$self->{'TEMPLATE_ORDER'}->[$count]} = '';
						} else {
							$self->{$self->{'TEMPLATE_ORDER'}->[$count]} = $change;
							Debug("Template field $self->{'TEMPLATE'}->{$self->{'TEMPLATE_ORDER'}->[$count]} is not blank, not invalidating template. (S3)");
						}
					}
				}
			}
		}
		my $cmd = qq~$GIT_PATH show --pretty="format:" --name-only $self->{'commit_id'} 2>&1~;
		Debug("Running Commad:  $cmd");
		chomp($self->{'files'} = `$cmd`);
		Debug("Response:\n$self->{'files'}");
		$self->{'files'} =~ s/^\n*//gs;
	} else {
		$self->{'wrong_user'}		= 1;
		$self->{'valid_template'}	= 0;
		Debug('Wrong user.  Invalid template.');
	}
	$self->extract_CQ_ids() if ($self->{'valid_template'});
	$self->check_overrides();
	return($self);
}

sub slurp_file {
	# This reads a text file into a string
	my $self = shift;
	my $file = shift;
	my $text = '';
	
	Debug("Loading $file");
	if (-e $file) {
		$text = do { local( @ARGV, $/ ) = $file ; <> } ; # Slurp!
		Debug("$file successfully loaded.");
	} else {
		Debug("$file does not exist!");
	}
	return($text);
}
sub trim {
	# This trims off space on the beginning and end of a string
	my $self	= shift;
	my $string	= shift;
	Debug("Trimming excess space from '$string'");
	$string =~ s/^\s+//g;
	$string =~ s/\s+$//g;
	Debug("Trimmed excess space from '$string'");
	return($string);
}
sub Debug {
	# This outputs debugging messages in a formatted and easy to read method.
	my $message	= shift;
	$message =~ s/\r//sg;
	my $date	= get_log_date(time);
	if ($DEBUG) {
		my ($package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require, $hints, $bitmask, $hinthash) = caller(1);
		($package, $filename, $line) = caller if ($line == 0);
		$package ||= 'main';
		$filename = uc($SCRIPT_NAME);
		$filename =~ s/\.PL$//;
		my $pkg = sprintf('%-66s',"$filename\:\:$package\:\:$subroutine()");
		$pkg =~ s/ /./g;
		if ($DEBUG == 1) {
			if ($FIRST) {
				my $log;
				$FIRST = 0;
				my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($DBG_FILENAME);
				my $ftime		= get_date(scalar(localtime($mtime)));
				my $this_time	= get_date(scalar(localtime()));
				if ($message =~ /START/) {
					$log	= "$message\n";
					$message	= '';
				}
				$log .= "Log File Last Modified Date: $ftime, Now: $this_time\n\n";
				if ($message ne '') {
					$log .= $message;
				}
				$message = $log;
			}
			$message =~ s/\n/\n\t/sg;
			my @msgs = split(/\n/,$message);
			foreach my $msg (@msgs) {
				if ($subroutine =~ /eval/) {
					($subroutine) = $msg =~ /\[(.+?)\(\)\] /;
					$msg =~ s/\[$subroutine\(\)\] //;
					$pkg = sprintf('%-66s',"$filename\:\:$package\:\:$subroutine()");
					$pkg =~ s/ /./g;
				}
				print STDERR sprintf("\e[32;40m%s\e[34m[\e[36m%s\e[34m[\e[33m%4d\e[34m]\e[37m %s\e[0m",$date,$pkg,$line,$msg),"\n";
				print STDERR "\n" if ($message =~ /=== END/);
			}
		} elsif ($DEBUG == 2) {
			open($HANDLE,'>>',"$SCRIPT_PATH/Logs/$LOG_FILENAME") if (! defined($HANDLE));
			$message =~ s/\n/\n\t/sg;
			my @msgs = split(/\n/,$message);
			foreach my $msg (@msgs) {
				if ($subroutine =~ /eval/) {
					($subroutine) = $msg =~ /\[(.+?)\(\)\] /;
					$msg =~ s/\[$subroutine\(\)\] //;
					$pkg = sprintf('%-66s',"$filename\:\:$package\:\:$subroutine()");
					$pkg =~ s/ /./g;
				}
				print $HANDLE sprintf('[%s][%s > %4d] %s',$date,$pkg,$line,$msg),"\n";
			}
		}
	}
	$SIG{'ALRM'} = \&timeout; # Place where timeout timer goes on timeout, just in case it was changed.
	alarm($HOOK_TIMEOUT); # This acts as a keep-alive timer.  So it must be reset periodically.
}
sub extract_CQ_ids {
	my $self = shift;

	# Filter out the system generated text
	$self->{'defect'} =~ s/<Please substitute this line.*?>//g;
	$self->{'defect'} =~ s/<MobC.*?\:.*?\:.*?\:.*?\:.*?>//gcs;
	$self->{'cqids'} = undef;
	my @temp = split(/\n/,$self->{'defect'});
	Debug("DEFECT=\n$self->{'defect'}\nTEMP >\n". join("\n",@temp));
	foreach my $line (@temp) {
		Debug("EXTRACTING LINE:	$line");
		$line =~ s/interim/interim/gsi;
		my %test = $line =~ /MobC\s*(\d+)(\s*\:*\s*interim)*/gcs;
		foreach my $id (keys %test) {
			my $interim = $test{$id};
			if ($interim =~ /interim/i) {
				$self->{'cqids'}->{"MobC$id"} = 'interim';
			} else {
				$self->{'cqids'}->{"MobC$id"} = 'normal';
			}
			Debug("Found ID=$id, MODE=$interim, " . $self->{'cqids'}->{"MobC$id"});
#				$line =~ s/MobC\s*\d+(\s*\:*\s*interim)*//;
		}
	}
	Debug("Complete.  Results:\n");
}
sub get_log_date {
	my $this_time = shift;

	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($this_time);
	$mon++;
	$year += 1900;
	return(sprintf('%02d/%02d/%d %02d:%02d:%02d',$mon,$mday,$year,$hour,$min,$sec));	
}
sub get_date {
	my $date_string = shift;
	my ($wday,$month,$day,$Time,$year) = split(/\s+/,$date_string);
	return("$month $day, $year");
}
sub parse_revisions {
	my $raw = shift;
	Debug("Parsing:\n$raw");

	my @lines = split(/\n/,$raw);
	my @results;
	while(scalar(@lines)) {
		my $fields;
		my $line = shift(@lines);
		if ($line =~ /^commit/) {
			$line =~ s/^commit\s+//;
			$fields->{'commit_id'}	= $line;
			$line = shift(@lines);
			if ($line =~ /^Merge:/) {
				$line =~ s/Merge:\s+//;
				$fields->{'merge'} = $line;
				$line = shift(@lines);
			}
			$line =~ s/^Author:\s+//;
			$fields->{'author'}		= $line;
			$line = shift(@lines);
			$line =~ s/^Date:\s+//;
			$fields->{'date'}		= $line;
			$line = shift(@lines);
			while(scalar(@lines) && $lines[0] !~ /^commit/) {
				$line = shift(@lines);
				$fields->{'message'} .= "$line\n";
			}
			push(@results,$fields);
		}
	}
	Debug('Parsed ' . scalar(@results) . " commits:\n" . Dumper(\@results));
	return(\@results);
}
sub check_hooks {
	my $self	= shift;
	my $dirpath	= shift;

	my $return_value = 0;
	Debug("Parsing for exact match to [$dirpath]");

	##########################################################################
	# This parses the Configuration XML into something more referenceable

	Debug("CONFIG.XML:\n" . Dumper($CONFIG_XML));
	my $hooks = XMLin($CONFIG_XML,'KeyAttr' => 'name','ForceArray' => 1, 'ContentKey' => '-content');
	Debug("Pre-processed HOOKS:\n" . Dumper($hooks));
	foreach my $name (keys %{$hooks->{'MASTER'}->[0]}) {
		if ($hooks->{'MASTER'}->[0]->{$name}->[0] =~ /true|1|yes/i) {
  			$self->{'MASTER'}->{$name} = 1;
		} elsif ($hooks->{'MASTER'}->[0]->{$name}->[0] =~ /false|0|no/i) {
  			$self->{'MASTER'}->{$name} = 0;
		}
	}
	Debug("MASTER:\n" . Dumper($hooks->{'MASTER'},$self->{'MASTER'}));
  	foreach my $tv (keys %{$hooks->{'CONFIG'}}) {
		if ($hooks->{'CONFIG'}->{$tv} =~ /true|1|yes/i) {
  			$self->{'CONFIG'}->{$tv} = 1;
		} elsif ($hooks->{'CONFIG'}->{$tv} =~ /false|0|no/i) {
  			$self->{'CONFIG'}->{$tv} = 0;
		} else {
  			$self->{'CONFIG'}->{$tv} = $self->trim($hooks->{'CONFIG'}->{$tv});
		}
  	}
	foreach my $ext (@{$hooks->{'TEXT_FILE_EXTENSIONS'}}) {
		push(@{$self->{'TEXT_FILE_EXTENSIONS'}},$self->trim($ext));
	}
	foreach my $template (@{$hooks->{'TEMPLATES'}}) {
		$self->{'TEMPLATE_ORDER'}->[$template->{'index'}]	= $self->trim($template->{'key'});
		$self->{'TEMPLATE'}->{$template->{'key'}}			= $self->trim($template->{'content'});
		$self->{'TEMPLATE_VALIDATE'}->{$template->{'key'}}	= $self->trim(lc($template->{'validate'}));
		$self->{'TEMPLATE_VALIDATE'}->{$template->{'key'}}	=~ s/true/1/i;
		$self->{'TEMPLATE_VALIDATE'}->{$template->{'key'}}	=~ s/false/0/i;
	}
	foreach my $project (keys %{$hooks->{'PROJECT'}}) {
		foreach my $hook (keys %{$hooks->{'PROJECT'}->{$project}}) {
			if ($hooks->{'PROJECT'}->{$project}->{$hook}->[0] =~ /true|1/i) {
				$hooks->{'PROJECT'}->{$project}->{$hook} = 1;
			} else {
				$hooks->{'PROJECT'}->{$project}->{$hook} = 0;
			}
			$self->{'HOOKS'}->{$project}->{$hook} = $hooks->{'PROJECT'}->{$project}->{$hook};
		}
	}
	Debug("Post-Processed HOOKS:\n" . Dumper($self->{'HOOKS'}));

	##########################################################################
	# This Kludge must be done for the time being.  It scans ALL manifests
	# for a match to the reference and assigns the proper settings for use
	# in processing.
	my $cached = 1;
    if (-e $CACHE_FILENAME) { # The Cache is refreshed daily.
    	my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($CACHE_FILENAME);
    	my $ftime		= get_date(scalar(localtime($mtime)));
    	my $this_time	= get_date(scalar(localtime()));
    	if ($this_time ne $ftime) {
    		$cached = 0;
    		Debug('Cache has expired.  Recalculating data.');
    	} else {
    		Debug('Cached data has not expired yet.  Using cached data.');
    	}
    } else {
    	$cached = 0;
    	Debug("Cache file $CACHE_FILENAME does not exist.  Let's remedy that.");
    }
    if ($cached && ! defined($PROJECTS)) { # Since this function is run for each commit, only load the cache if not already in RAM.
    	Debug("Loading in cached manifest and setting data from $CACHE_FILENAME");
    	my $cache = $self->slurp_file($CACHE_FILENAME);
    	eval($cache);
    }
	foreach my $project (keys %{$self->{'HOOKS'}}) {
		$project =~ s/\*+|\s+//g;
		if (! exists($PROJECTS->{$project})) { # Only get the data if we don't already have it.
			$cached			= 0; # Indicate the cache needs to be rewritten at the end of the process.
			my $cmd			= "cd $REPOS_PATH; $GIT_PATH show $project:default.xml";
			chomp(my $xml_file	= `$cmd`);
			if ($xml_file eq '') {
				Debug("Project $project has no manifest!!");
				return($return_value);
			}
			$PROJECTS->{$project} = XMLin($xml_file, 'ForceArray' => 1, 'KeyAttr' => '');
			Debug("PROJECT $project XML:\n" . Dumper($PROJECTS->{$project}));
    		foreach my $remote (@{$PROJECTS->{$project}->{'remote'}}) {
				Debug("$project REMOTE:\n" . Dumper($remote));
    			$remote->{'fetch'} =~ s/$GIT_URIS_SEARCH/$GIT_URIS_REPLACE/;
    			$self->{'REMOTES'}->{$remote->{'name'}} = $remote->{'fetch'};
    		}
    		foreach my $repo (@{$PROJECTS->{$project}->{'project'}}) {
				Debug("REPO:\n" . Dumper($PROJECTS->{$project}->{'default'}->[0],$repo));
    			if (! defined($repo->{'revision'})) {
    				$repo->{'revision'} = $PROJECTS->{$project}->{'default'}->[0]->{'revision'};
    			}
    			if (! defined($repo->{'remote'})) {
    				$repo->{'remote'} = $PROJECTS->{$project}->{'default'}->[0]->{'remote'};
    			}
    			$self->{'BASE_PATH'}	= $self->{'REMOTES'}->{$repo->{'remote'}}; # . '.git';
    			$self->{'BASE_PATH'}	.= '/' if ($self->{'BASE_PATH'} !~ /\/$/); # Make sure it has a slash at the end
    			$self->{'REPO_PATH'}->{$repo->{'revision'}}	= "$self->{'BASE_PATH'}$repo->{'name'}.git"; # According to Sirish, use "name" instead of "path"
    			Debug("REMOTE:	$repo->{'remote'}\nREVISION:	$repo->{'revision'}\nREPO PATH:	$self->{'REPO_PATH'}->{$repo->{'revision'}}\nBASE PATH:	$self->{'BASE_PATH'}");

    			##################################################################
    			# Logically OR the settings from all matching projects.
    
    			Debug("Working on $repo->{'revision'}");
    			Debug('ORing settings (Kludge, but must be done)');
    			foreach my $hook (keys %{$self->{'HOOKS'}->{$project}}) {
    				$self->{'REFERENCES'}->{$repo->{'revision'}}->{$hook} |= $self->{'HOOKS'}->{$project}->{$hook};
					Debug("$project > hook $repo->{'revision'} > $hook = $self->{'REFERENCES'}->{$repo->{'revision'}}->{$hook}");
    			}
    			##################################################################
    		}
		} else {
			Debug("Project:  $project  CACHED");
		}
	}
	Debug("REFERENCES:\n" . Dumper($self->{'REFERENCES'}));
	if ($cached) { # Dump the HOOKS portion to a file for quicker parsing
		Debug('No need to write to Cache file');
	} else {
		Debug("Saving generated tree to cache file:  $CACHE_FILENAME");
		open(my $FILE,'>',$CACHE_FILENAME);
		print $FILE Dumper($self->{'HOOKS'},$PROJECTS,$self->{'REFERENCES'},$self->{'BASE_PATH'},$self->{'REPO_PATH'},$self->{'REMOTES'});
		close($FILE);
		system("chmod +w $CACHE_FILENAME");
	}
	return($return_value);
}
sub load_config {
	my $self	= shift;
	my $dirpath	= shift;

	Debug('Loading configuration');
	my $return_value = 0;
	if (! defined($dirpath) || $dirpath eq '') {
		Debug('dirpath not defined or empty!');
		return($return_value);
	}
	$dirpath =~ s/^\s*//g;
	$dirpath =~ s/\s*$//g;
	$dirpath =~ s/\/$//; # trim any trailing / in path
	Debug('Configuration loaded');

	my $response = $self->check_hooks($dirpath);
	return($response); # Don't monitor
}
sub path_sort {
	my $A	= substr($a,2);
	my $B	= substr($b,2);

	my @A_	= split(/\//,$A);
	my @B_	= split(/\//,$B);
	$A		= scalar(@A_);
	$B		= scalar(@B_);
	if ($A == $B) {
		$A = pop(@A_);
		$B = pop(@B_);
		return($A cmp $B);
	}
	return($A <=> $B);
}
sub timeout {
	$DEBUG = 1;
	Debug('==== Hook appears to be frozen.	Killing...Exiting... END > 1 =================================');
	exit(1);
}
sub grab_input {
	# ARGV must have the parameters in a specific order.  This is a GIT standard.
	chomp(@ARGV);
	my $database = {
		'old-value'	=> $ARGV[0],
		'new-value'	=> $ARGV[1],
		'ref-name'	=> $ARGV[2]
	};
	Debug("Command Line Parameters:\n" . Dumper($database));

	$database->{'trimmed-ref-name'} = $database->{'ref-name'};
	$database->{'trimmed-ref-name'} =~ s#refs/heads/##;

	$database->{'branch'} = 0;
	if ($ARGV[0] eq '0000000000000000000000000000000000000000' || $ARGV[1] eq '0000000000000000000000000000000000000000') {
		Debug('Old/New-Value is all zeros.  It is a new branch.  No need to process.');
		$database->{'branch'} = 1;
		return($database); # Bail out.
	}

	# All of this is "magic" don't disturb the wizard.  It makes these various calls to find the revisions list and other important data
	my $cmd = "$GIT_PATH rev-parse $database->{'ref-name'}";
	Debug("Running command '$cmd'");
	chomp($database->{'rev-parse'}	= `$cmd`);
	$database->{'rev-parse-cmd'}	= $cmd;
	Debug('REV-PARSE:	' . $database->{'rev-parse'});

	$cmd = "$GIT_PATH rev-parse $database->{'old-value'}";
	Debug("Running command '$cmd'");
	chomp($database->{'old-rev'}	= `$cmd`);
	$database->{'old-rev-cmd'}		= $cmd;
	Debug('OLD-REV:	' . $database->{'old-rev'});

	$cmd = "$GIT_PATH rev-parse $database->{'new-value'}";
	Debug("Running command '$cmd'");
	chomp($database->{'new-rev'}	= `$cmd`);
	$database->{'new-rev-cmd'}		= $cmd;
	Debug('NEW-REV:	' . $database->{'new-rev'});

	$cmd = "$GIT_PATH cat-file -t $database->{'old-rev'} 2> /dev/null";
	Debug("Running command '$cmd'");
	chomp($database->{'old-rev-type'}	= `$cmd`);
	$database->{'old-rev-type-cmd'}		= $cmd;
	Debug('OLD-REV-TYPE:  ' . $database->{'old-rev-type'});

	$cmd = "$GIT_PATH cat-file -t $database->{'new-rev'} 2> /dev/null";
	Debug("Running command '$cmd'");
	chomp($database->{'new-rev-type'}	= `$cmd`);
	$database->{'new-rev-type-cmd'}		= $cmd;
	Debug('NEW-REV-TYPE: ' . $database->{'new-rev-type'});

	$cmd = "$GIT_PATH rev-parse --not --branches | /bin/grep -v $database->{'rev-parse'} | $GIT_PATH rev-list --pretty $database->{'old-rev'}..$database->{'new-rev'}";
	Debug("Running command '$cmd'");
	$database->{'raw_revisions_list'}		= `$cmd`;
	$database->{'raw_revisions_list_cmd'}	= $cmd;

	Debug('RAW_REVISIONS_LIST:	' . $database->{'raw_revisions_list'});

	$database->{'revisions_list'}		= parse_revisions($database->{'raw_revisions_list'});
	$database->{'revisions_list_cmd'}	= $cmd;
	Debug('REVISIONS_LIST:	' . Dumper($database->{'revisions_list'}));

	if (! defined($database->{'revisions_list'}) || scalar($database->{'revisions_list'}) == 0) {
		Debug('The Revisions List is empty!!  Rejecting push and sending email to scm list.');
		queue_errors('',qq~

<H2>FATAL ERROR IN GIT HOOK!!  The revisions list for the push is empty!</H2>

<H3>THE VALUES PASSED TO THE HOOK</H3>

<PRE>
<STRONG>OLD-VALUE:  $database->{'old-value'}
<STRONG>NEW-VALUE:  $database->{'new-rev'}
 <STRONG>REF-NAME:  $database->{'ref-name'}
 
     USER:  $ENV{'USER'}
</PRE>

<H3>PARSED INFORMATION FROM THOSE VALUES:</H3>
<PRE>
         REV-PARSE Command:  $database->{'rev-parse-cmd'}
                 REV-PARSE:  $database->{'rev-parse'}
                 
           OLD-REV Command:  $database->{'old-rev-cmd'}
                   OLD-REV:  $database->{'old-rev'}

           NEW-REV Command:  $database->{'new-rev-cmd'}
                   NEW-REV:  $database->{'new-rev'}

      OLD-REV-TYPE Command:  $database->{'old-rev-type-cmd'}
              OLD-REV-TYPE:  $database->{'old-rev-type'}

      NEW-REV-TYPE Command:  $database->{'new-rev-type-cmd'}
              NEW-REV-TYPE:  $database->{'new-rev-type'}

RAW-REVISIONS-LIST Command:  $database->{'raw_revisions_list_cmd'}
        RAW-REVISIONS-LIST:
$database->{'raw_revisions_list'}

    REVISIONS-LIST Command:  $database->{'revisions_list_cmd'}
            REVISIONS-LIST:
~ . Dumper($database->{'revisions_list'}) . qq~
</PRE>
~,'N/A');
#		send_error_email({'email' => $ENV{'USER'} . '@broadcom.com,mps-scm-cq-list@broadcom.com,mps-scm-grid-list@broadcom.com'});
		send_error_email({'email' => $ENV{'USER'} . '@broadcom.com'});
		Debug('==== END -> 1 ' . '='x72);
		exit(1);
	}

	return($database);
}
sub queue_errors {
	my $self	= shift;
	my $message	= shift;
	my $id		= shift;

	push(@ERRORS,{'message' => $message,'commit_id' => $id});
	return(1);
}
sub send_error_email {
	# Only sends email if there are errors queued
	my $self	= shift;

	my $tline	= '='x79;
	my $line	= '-'x79;

	if (scalar(@ERRORS)) {
		my $html = qq~\n<HTML>\n\t<HEAD>
		<STYLE>
			body {
				margin: 10px;
				background: white;
				color: black;
				font-face: Arial;
				font-family: Arial, Helvetica, Sans Serif;
			}
			div {
				border: 1px dotted black;
				padding: 0px 10px 10px 10px;
			}
			h2 {
				background: maroon;
				color: white;
				padding: 2px 8px 2px 8px;
			}
			table {
				width: 100%;
			}
			th {
				text-align: left;
				background: maroon;
				color: white;
				padding: 1px 5px 1px 5px;
				vertical-align: top;
			}
			td {
				text-align: left;
				padding: 1px;
				vertical-align: top;
				background: black;
				color: white;
			}
			a {
				color: red;
			}
			.red {
				color: red;
			}
			.green {
				color: #00FF00;
			}
			.yellow {
				color: #FFFF00;
			}
			.indent {
				padding-left: 20px;
			}
		</STYLE>\n\t</HEAD>\n\t<BODY>\n~;
		my $body	= '';
		my $insert	= $self->{'insert'}	|| 'error';
		my $cc		= $self->{'cc'}		|| '';
		my $bcc		= $self->{'bcc'}	|| '';
		foreach my $error (@ERRORS) {
			$body	.= $error->{'message'};
		}
		my $text = $body;

		$html .= $body;

		$text =~ s/^\s+</</mg;
		$text =~ s/&lt;/</gs;
		$text =~ s/&gt;/>/gs;
		$text =~ s/&nbsp;/ /gs;
		$text =~ s/<(BR|\/TR|\/P)>\n*/\n/igs;
		$text =~ s/<\/(TH|TD)>\n+/<\/$1>/sg;
		$text =~ s/<\/TH>/:  /sg;
		$text =~ s/<\/*(DIV|H|P|SPAN|PRE|STRONG|BLOCKQUOTE|UL|TABLE|TR|TH|TD|A).*?>//igs;
		$text =~ s/<LI>(.+?)<\/LI>/\t*  $1/igs;
		$text =~ s/<HR>/$line/isg;
		$text =~ s/\n\n\n+/\n\n/gs;
		$text =~ s/^(ERROR|WARNING):  $/=================== $1 =====================/mg;
		$html .= "</BODY>\n</HTML>\n";
		$cc = $bcc = '' if ($TEST_MODE); # No spamming while testing.
		my $msg = MIME::Lite->new(
			'To'		=> $self->{'email'},
			'Cc'		=> $cc,
			'Bcc'		=> $bcc,
			'From'		=> 'no-reply@broadcom.com',
			'Subject'	=> "GIT pre-receive hook : $insert report",
			'Type'		=> 'multipart/related'
		);
		$msg->attach(
			'Type'	=> 'text/html',
			'Data'	=> $html
		);
		$msg->attach(
			'Type'	=> 'text/plain',
			'Data'	=> $text
		);
		Debug("Sending the following email:\n" . Dumper($msg));
		eval {
			$msg->send();
		};
		Debug("SendMail error:  $@") if ($@);
		@ERRORS = ();
		print "GIT pre-receive hook : $insert report\n\n$text\n\n" if ($self->{'CONFIG'}->{'print_errors'});
		return(1);
	}
	return(0);
}
sub check_overrides {
	my $self = shift;
	my $write_to_log = 0;
	foreach my $name (qw( CODE_REVIEW CCB_APPROVAL FILE_VALIDATION DEFCONFIGS CHECKPATCH )) {
		my $search = "BYPASS_$name";
		if ($self->{'description'} =~ /<<$search>>/s) {
			$self->{lc($search)} = 1;
			$write_to_log = 1;
		} else {
			$self->{lc($search)} = 0;
		}
	}
	if ($write_to_log) {
		my $file;
		if (! -e "$SCRIPT_PATH/Logs/override_log.txt") {
			open($file,'>',"$SCRIPT_PATH/Logs/override_log.txt");
			print $file "# USER, DATE/TIME, COMMIT ID, BYPASS_CCB_APPROVAL, BYPASS_CODE_REVIEW, BYPASS_FILE_VALIDATION, BYPASS_DEFCONFIGS, BYPASS_CHECKPATCH\n\n";
		} else {
			open($file,'>>',"$SCRIPT_PATH/Logs/override_log.txt");
		}
		print $file $self->{'userid'},',',get_log_date(time),',',$self->{'commit_id'},',',$self->{'bypass_ccb_approval'},',',$self->{'bypass_code_review'},',',$self->{'bypass_file_validation'},',',$self->{'bypass_defconfigs'},',',$self->{'bypass_checkpatch'},"\n";
		close($file);
	}
}
sub scan_files_for_eols {
	my $self = shift;

	my @files				= split("\n",$self->{'files'});
	my $errors : shared		= 0;
	my $error_msg : shared	= '';
	my @thr;
	foreach my $file (@files) {
		$file = $self->trim($file);
		next if ($file eq '');

		# Thread throttling to prevent bogging down the server.
		# If the maximum amount of threads are running, then wait for at least one to finish.
		while (threads->list(threads::running) > $self->{'CONFIG'}->{'max_threads'}) {
			threads->yield(); # This prevents CPU hogging.
		}
		# Clean up from any finished threads.
		my @thr = threads->list(threads::joinable);
		foreach my $thread (@thr) {
			$thread->join();
		}
		push(@thr,threads->create(
			sub {
				my ($ext)		= ($file =~ /\.(.+?)$/);
				my $text		= '';
				if ($ext ne '' && grep(/$ext/i,@{$self->{'TEXT_FILE_EXTENSIONS'}})) {
					Debug("$file is a text file, scanning for validity.");
					my $cmd = "$GIT_PATH show $self->{'commit_id'}:$file";
					Debug("Running command '$cmd'");
					$text = `$cmd`;
					if ($text =~ /\r/s) { # Check for any ^M
						{
							lock($errors);
							$errors++;
						}
						{
							lock($error_msg);
							$error_msg .= "<LI>$file</LI>\n";
						}
						Debug("$file is a Windows format text file!  REJECTED.");
					} else {
						Debug("$file is a valid Linux format text file.  PASSED");
					}
				} elsif ($ext eq '') { # file with no extension may be a valid Linux script text file too.
					my $cmd = "$GIT_PATH show $self->{'commit_id'}:$file";
					Debug("Running command '$cmd'");
					$text = `$cmd`;
					if (substr($text,0,2) eq '#!') {
						Debug("$file is a text file, scanning for validity.");
						if ($text =~ /\r/s) { # Check for any ^M
							{
								lock($errors);
								$errors++;
							}
							{
								lock($error_msg);
								$error_msg .= "$file\n";
							}
							Debug("$file is a Windows format text file!  REJECTED.");
						} else {
							Debug("$file is a valid Linux format text file.  PASSED");
						}
					}
				}
			}
		));
	}
	Debug("Waiting for threads to finish");
	while (threads->list(threads::running)) {
		threads->yield();
	}
	my @thr = threads->list(threads::joinable);
	foreach my $thread (@thr) {
		$thread->join();
	}
	Debug("There were $errors text file errors.");
	if ($errors) {
		$self->{'error_msg'} .= "<UL>$error_msg</UL>";
	}
	return($errors);
}
sub check_defconfigs {
	# Checks for defconfigs missing in commmits based on the HEAD and first part of the filename
	my $self			= shift; # pull in the object
	my $start_commit	= shift;
	my $end_commit		= shift;
	my $ref_name		= $self->{'trimmed-ref-name'};
	my $ref_index		= $self->{'label'};

	Debug("Parameters received into subroutine:\nSTART COMMIT:  $start_commit\nEND COMMIT:  $end_commit\nREF-NAME:  $ref_name\nREF-INDEX:  $ref_index");
	# Get the list of defconfigs in all the commits
	my $cmd = qq~$GIT_PATH diff --name-only --pretty="format:" $start_commit $end_commit | /bin/grep -P "arch/arm/configs/(.+?)defconfig"~;
	$cmd	= qq~$GIT_PATH show --pretty="format:" --name-only $start_commit | /bin/grep -P "arch/arm/configs/(.+?)defconfig"~ if ($start_commit eq $end_commit);
	Debug("Running command:  $cmd");
	my $response = `$cmd`;
	Debug("Command returned:\n$response");
	my @files		= split("\n",$response);
	if (scalar(@files)) {
		# Get the master list of defconfigs in HEAD
		my ($platform)	= $files[0] =~ /^arch\/arm\/configs\/(.+?)_/;
		Debug("The platform is '$platform'");
		$cmd	= qq~$GIT_PATH ls-tree -r --name-only $ref_name | /bin/grep -P "arch/arm/configs/$platform(.+?)defconfig"~;
		Debug("Running command:  $cmd");
		$response = `$cmd`;
		Debug("Command returned:\n$response");
		my @defconfigs	= split("\n",$response);

		my %count;
		map $count{$_}++,@files,@defconfigs;
		Debug("Array Diff hash:\n" . Dumper(\%count));
		foreach my $Key (keys %count) {
			if ($count{$Key} == 1) {
				$self->{'error_msg'} .= qq~<LI><SPAN class="red">MISSING:</SPAN>&nbsp; $Key</LI>~;
			}
		}
		if (defined($self->{'error_msg'}) && $self->{'error_msg'} ne '') {
			my $good;
			foreach my $file (@files) {
				$good .= qq~<LI><SPAN class="green">OK:</SPAN>&nbsp; $file</LI>~;
			}
			$self->{'error_msg'} = "<UL>$good</UL><UL>$self->{'error_msg'}</UL>";
			return(1);
		}
	} else {
		Debug('No defconfigs in this push');
	}
	return(0);
}
sub checkpatch {
	# Runs checkpatch per commit
	my $self = shift; # pull in the object

	if (($THIS_PATH =~ /kernel/i && $THIS_PATH !~ m#kernel/arch/arm/mach-(rhea/include/mach/rdb|capri/include/mach/rdb)#) || ($self->{'files'} =~ /kernel/i && $self->{'files'} !~ m#kernel/arch/arm/mach-(rhea/include/mach/rdb|capri/include/mach/rdb)#)) {
		Debug('Kernel path detected.  Checkpatch continuing');
		my $good;
		my $cmd = qq~$GIT_PATH show --pretty="format:" $self->{'commit_id'} | $SCRIPT_PATH/checkpatch.pl --notree --nosignoff - 2>&1~;
		Debug("Running Command:  $cmd");
		my $response = `$cmd`;
		Debug("Response:\n$response");
		if ($response !~ /Your patch has no obvious style problems and is ready for submission/i) {
			$self->{'error_msg'} .= qq~<pre>$response</pre>~;
			return(1);
		}
	} else {
		Debug("We are not in a kernel path (or is an ignored path):  $THIS_PATH.  Checkpatch bypassed.");
	}

	return(0);
}
sub perforce_auto_submit {
	my $self = shift;
	my $cmd = "cd $MSP_GIT_REPO_PATH;$GIT_P4 rebase;$GIT_P4 submit 2>&1";
	Debug("Running command:  $cmd");
	alarm($HOOK_TIMEOUT * 3); # We allow extra time for this operation, as Perforce is as slow as molasses in January.
	my $results = `$cmd`;
	Debug("Command returned:\n$results");
	if ($? || $results =~ /error|cannot|conflict/i) {
		Debug("Problems with GIT-P4:\n$results");
		$self->{'error_msg'} .= "<PRE>$results</PRE>";
		return(0);
	} else {
		Debug('Files submission COMPLETE');
		return(1);
	}
}

1;

